package com.rainbow.chat.test.socket;

import com.alibaba.fastjson.JSON;
import com.rainbow.chat.common.ChatMsg;
import com.rainbow.chat.common.ChatOnline;
import com.rainbow.chat.common.ChatUtil;
import com.rainbow.chat.enums.ChatMsgType;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: Usher
 * @Description:
 * 回调处理类,继承SimpleChannelInboundHandler处理出站入站数据，模板设计模式，让主要的处理逻辑保持不变，让变化的步骤通过接口实现来完成
 */
public class SocketServerHandler extends SimpleChannelInboundHandler<String>{
    // 连接的channel
    static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    static ChannelGroup channel1 = new DefaultChannelGroup("chatRoom1",GlobalEventExecutor.INSTANCE);
    static ChannelGroup channel2 = new DefaultChannelGroup("chatRoom2",GlobalEventExecutor.INSTANCE);
    private static final AttributeKey<Integer>  channelUid = AttributeKey.valueOf("uid");
    // 登陆用户 暂支持每人 一个设备登陆 最多1000同时在线 , 启动时查询群组信息
    static Map<Integer, ChatOnline> onlineList = new ConcurrentHashMap(1000);
    // 聊天室1/2 最多200同时在线 : 在线的聊天室用户
    static Map<Integer, ChannelGroup> chatRooms = new ConcurrentHashMap(3){{
        put(1, channel1);
        put(2, channel2);
    }};
    /**
     * 每当从客户端有消息写入时
     * @param ctx
     * @param s
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("[" + channel.remoteAddress() + " 说：]" + s);
        ChatMsg chatMsg  = JSON.parseObject(s, ChatMsg.class);
        if(chatMsg!=null){
            String      msg     = chatMsg.getMsg();
            Integer     toUid   = chatMsg.getToUid();
            ChatMsgType type    = chatMsg.getType();
            Integer     fromUid = chatMsg.getFromUid();
            Integer     groupId = chatMsg.getGroupId();
            if(ChatMsgType.ACTION_INIT.equals(type)) { //2 init
                channel.attr(channelUid).set(fromUid);
                ChatOnline chatOnline = new ChatOnline();
                chatOnline.setChannelId(channel.id());
                onlineList.put(fromUid,chatOnline);
                ctx.writeAndFlush(ChatUtil.getTipMsg("初始化成功"));
            }else if(ChatMsgType.ACTION_GROUP_QUIT.equals(type)){ //12 退出群聊
                if(!onlineList.containsKey(fromUid)){
                    ctx.writeAndFlush(ChatUtil.getTipMsg("需要 init"));
                    return;
                }
                if(chatRooms.containsKey(groupId)) {
                    ChannelGroup chatRoom = chatRooms.get(groupId);
                    chatRoom.remove(channel);
                    chatRoom.writeAndFlush(ChatUtil.getTipMsg(fromUid + "退出群聊" + groupId));
                }else{
                    ctx.writeAndFlush(ChatUtil.getTipMsg("退出群组"+ groupId+"失败"));
                }
            }else if(ChatMsgType.ACTION_GROUP_ADD.equals(type)){ //11 加入群聊
                if(!onlineList.containsKey(fromUid)){
                    ctx.writeAndFlush(ChatUtil.getTipMsg("需要 init"));
                    return;
                }
                if(chatRooms.containsKey(groupId)) {
                    ChannelGroup chatRoom = chatRooms.get(groupId);
                    Integer oldGroup = onlineList.get(fromUid).getGroup();
                    if(!chatRoom.contains(channel)){
                        // 加入群聊
                        chatRoom.add(channel);
                        chatRoom.writeAndFlush(ChatUtil.getTipMsg(fromUid + "进入群聊" + groupId));
                        onlineList.get(fromUid).setGroup(groupId);
                        // 退出上次的群组
                        if(!groupId.equals(oldGroup) && (chatRooms.containsKey(oldGroup))){
                            ChannelGroup oldRoom = chatRooms.get(oldGroup);
                            oldRoom.remove(channel);
                            oldRoom.writeAndFlush(ChatUtil.getTipMsg(fromUid + "退出群聊" + groupId));
                        }
                    }
                }else{
                    ctx.writeAndFlush(ChatUtil.getTipMsg("进入群组"+ groupId+"失败"));
                }
            }else if(ChatMsgType.BOARDCAST.equals(type)){ //4 公告
                // TODO 消息写入数据库表
                channels.writeAndFlush(ChatUtil.getTipMsg(msg));
            }else if(ChatMsgType.CHAT.equals(type)){ //3 私聊/群聊
                if(!onlineList.containsKey(fromUid)){
                    ctx.writeAndFlush(ChatUtil.getErrMsg("需要 init"));
                    return;
                }
                // 客户端自行判断是否为自己发的
                // TODO 消息写入数据库表
                if(groupId>0){ // 群聊
                    if(chatRooms.containsKey(groupId)){
                        if(chatRooms.get(groupId).contains(channel)){
                            chatRooms.get(groupId).writeAndFlush(ChatUtil.getGroupMsg(fromUid,groupId,msg));
                        }else{
                            ctx.writeAndFlush(ChatUtil.getErrMsg("请先入群"));
                        }
                    }else{
                        ctx.writeAndFlush(ChatUtil.getErrMsg("未知群组"));
                    }
                }else{
                    if(toUid>0){
                        if(onlineList.containsKey(toUid)){ //在线
                            channels.find(onlineList.get(toUid).getChannelId()).writeAndFlush(ChatUtil.getChatMsg(fromUid,toUid,msg));
                        }else{
                            ctx.writeAndFlush(ChatUtil.getTipMsg(toUid +" 离线中"));
                        }
                    }else{
                        ctx.writeAndFlush(ChatUtil.getTipMsg("接收对象错误"));
                    }
                }
            }else if(ChatMsgType.TIP.equals(type)){ //1 system
                if("ping".equalsIgnoreCase(msg)){
                    ctx.writeAndFlush(s+"\n");
                }
            }else{
                ctx.writeAndFlush(ChatUtil.getErrMsg("消息类型错误"));
            }
        }else{
            ctx.writeAndFlush(ChatUtil.getErrMsg("格式错误"));
        }
    }


    /**
     * 心跳检测
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent){
            IdleStateEvent e = (IdleStateEvent) evt;
            switch (e.state()){
                case READER_IDLE:
                    ctx.writeAndFlush(ChatUtil.getTipMsg("ping"));break;
                case WRITER_IDLE:
                case ALL_IDLE:
                default:
                    break;
            }
        }
//        ctx.fireUserEventTriggered(evt);
    }
    public void offline(Channel channel,String msg,Boolean cast){
        System.out.println("[" + channel.remoteAddress() + "]离线 : " + msg);
        if(cast){ channels.remove(channel); }
        Integer uid = channel.attr(channelUid).get();
        if(null!= uid){
            ChatOnline chatOnline = onlineList.get(uid);
            onlineList.remove(uid);
            if(chatOnline!=null){
                Integer groupId = chatOnline.getGroup();
                if(chatRooms.containsKey(groupId)){
                    // 退群
                    ChannelGroup chatRoom = chatRooms.get(groupId);
                    chatOnline.setGroup(0);
                    chatRoom.remove(channel);
                    chatRoom.writeAndFlush(ChatUtil.getTipMsg(uid + msg));
                }
            }
        }
    }

    /**
     * 当有客户端连接时，handlerAdded会执行,就把该客户端的通道记录下来，加入队列
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(ChatUtil.getTipMsg("usage: type[2-初始化/3-单群聊/4-广播/11-入群/12-退群]" +
                ",group[1/2],to,msg"));
        channels.add(ctx.channel()); //加入队列
    }

    /**
     * 异常处理
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("error : " + ctx.channel().id() + " : " + cause.getMessage());
        ctx.close();
    }

    /**
     * 当服务器监听到客户端活动时
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("[" + ctx.channel().remoteAddress() + "]: 在线");
    }
    /**
     * 离线
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        offline(ctx.channel(),"离线",false);
    }

    /**
     * 断开连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        offline(ctx.channel(),"断开连接",true);
    }
}